/*
 * JSPoint.js
 * Arithmetic in the group of points on a BN elliptic curve over GF(p).
 * Copyright (C) Jorge H. C. Lucema.
 */

function JSPoint(E,x,y,z) {
    if (arguments.length === 1) {
        if (E instanceof JSCurve) {
            this.E = E;
            this.x = E.bn._1;
            this.y = E.bn._1;
            this.z = E.bn._0;
        }
        if (E instanceof JSPoint) {
            var Q = E;
            this.E = Q.E;
            this.x = Q.x;
            this.y = Q.y;
            this.z = Q.z;
        }
    }
    if (arguments.length === 3) {
        if ((x instanceof BigInteger) && (y instanceof BigInteger)) {
            this.E = E;
            var p = E.bn.p; // shorthand
            this.x = x;
            this.y = y;
            this.x = x.mod(p);
            this.y = y.mod(p);
            this.z = E.bn._1; // normalized
        }
    }
    if (arguments.length === 4) {
        this.E = E;
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

/*
 * Check whether this is the point at infinity
 * @returns {Boolean}
 */

function isZero() {
    return this.z.signum() === 0;
}

/*
 * Compute this + Q
 * @param {JSPoint} Q
 * @returns {JSPoint|add.Q}
 */

function add(Q) {
    if (this.isZero()) {
        return Q;
    }
    if (Q.isZero()) {
        return this;
    }
    var p = this.E.bn.p;
    var X1 = this.x, Y1 = this.y, Z1 = this.z, X2 = Q.x, Y2 = Q.y, Z2 = Q.z, Z1Z1 = this.E.bn._1, Z2Z2 = this.E.bn._1, U1 = this.x, U2 = Q.x, S1 = this.y, S2 = Q.y, H, I, J, R, V, X3, Y3, Z3;
    var Z1is1 = (Z1.compareTo(this.E.bn._1) === 0);
    var Z2is1 = (Z2.compareTo(this.E.bn._1) === 0);
    if (!Z1is1) {
        Z1Z1 = Z1.multiply(Z1).mod(p); // Z1Z1 = Z1^2
        U2 = X2.multiply(Z1Z1).mod(p); // U2 = X2*Z1Z1
        S2 = Y2.multiply(Z1).multiply(Z1Z1).mod(p); // S2 = Y2*Z1*Z1Z1
    }
    if (!Z2is1) {
        Z2Z2 = Z2.multiply(Z2).mod(p); // Z2Z2 = Z2^2
        U1 = X1.multiply(Z2Z2).mod(p); // U1 = X1*Z2Z2
        S1 = Y1.multiply(Z2).multiply(Z2Z2).mod(p); // S1 = Y1*Z2*Z2Z2
    }
    if (U1.compareTo(U2) === 0) {
        if (S1.compareTo(S2) === 0) {
            return this.twice(1);
        } else {
            return this.E.infinity;
        }
    }
    H = U2.subtract(U1); // H = U2-U1
    I = H.shiftLeft(1);
    I = I.multiply(I).mod(p); // I = (2*H)^2
    J = H.multiply(I);//.mod(p); // J = H*I
    R = S2.subtract(S1).shiftLeft(1); // r = 2*(S2-S1)
    V = U1.multiply(I);//.mod(p); // V = U1*I
    X3 = R.multiply(R).subtract(J).subtract(V.shiftLeft(1)).mod(p); // X3 = r^2-J-2*V
    Y3 = R.multiply(V.subtract(X3)).subtract(S1.multiply(J).shiftLeft(1)).mod(p); // Y3 = r*(V-X3)-2*S1*J
    if (Z2is1) {
        if (Z1is1) {
            Z3 = H.shiftLeft(1).mod(p); // Z3 = 2*H
        } else {
            Z3 = Z1.multiply(H).shiftLeft(1).mod(p); // Z3 = ((Z1+1)^2-Z1Z1-1)*H
        }
    } else {
        if (Z1is1) {
            Z3 = Z2.multiply(H).shiftLeft(1).mod(p); // Z3 = ((1+Z2)^2-1-Z2Z2)*H
        } else {
            Z3 = Z1.add(Z2);
            Z3 = Z3.multiply(Z3).subtract(Z1Z1).subtract(Z2Z2).multiply(H).mod(p);// Z3 = ((Z1+Z2)^2-Z1Z1-Z2Z2)*H
        }
    }
    return new JSPoint(this.E, X3, Y3, Z3);
}

function normalize() {
    if (this.z.signum() === 0 || this.z.compareTo(this.E.bn._1) === 0) {
        return this; // already normalized
    }
    var p = this.E.bn.p; // shorthand
    var zinv = null;
    try {
        zinv = this.z.modInverse(p);
    } catch (a) {
        //
    }
    var zinv2 = zinv.multiply(zinv); // mod p
    return new JSPoint(this.E, this.x.multiply(zinv2).mod(p), this.y.multiply(zinv).multiply(zinv2).mod(p), this.E.bn._1);
}

/*
 * Left-shift this point by a given distance n, i.e. compute (2^^n)*this.
 * @param {Integer} n the shift amount.
 * @return (2^^n)*this.
 */

function twice(n) {
    // EDF doubling formulas:
    // <http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l>
    // <http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-mdbl-2007-bl>
    var A, B, C, S, M, X = this.x, Y = this.y, Z = this.z;
    var p = this.E.bn.p; // shorthand
    while (n-- > 0) {
        A = X.multiply(X); // A = X1^2 (modular reduction is irrelevant)
        B = Y.multiply(Y).mod(p); // B = Y1^2
        C = B.multiply(B); // C = B^2 (modular reduction is irrelevant)
        S = X.add(B);
        S = S.multiply(S).subtract(A).subtract(C).shiftLeft(1).mod(p); // S = 2*((X1+B)^2-A-C)
        M = A.multiply(this.E.bn._3).mod(p); // M = 3*A
        X = M.multiply(M).subtract(S.shiftLeft(1)).mod(p); // X3 = M^2-2*S
        Z = Y.multiply(Z).shiftLeft(1).mod(p); // Z3 = 2*Y1*Z1
        Y = M.multiply(S.subtract(X)).subtract(C.shiftLeft(3)).mod(p); // Y3 = M*(S-X3)-8*C
    }
    return new JSPoint(this.E, X, Y, Z);
}

/*
 * Compute ks*this + kr*Y.  This is useful in the verification part of several signature algorithms,
 * and (hopely) faster than two scalar multiplications.
 * @param {BigInteger} ks scalar by which this point is to be multiplied.
 * @param {BigInteger} kr scalar by which Y is to be multiplied.
 * @param {BNPoint} Y a curve point.
 * @returns {ks*this + kr*Y}
 */

function simultaneous(ks, kr, Y) {
    var hV = new Array(16);
    var P = this.normalize();
    Y = Y.normalize();
    if (ks.signum() < 0) {
        ks = ks.negate();
        P = P.negate();
    }
    if (kr.signum() < 0) {
        kr = kr.negate();
        Y = Y.negate();
    }
    hV[0] = this.E.infinity;
    hV[1] = P;
    hV[2] = Y;
    hV[3] = P.add(Y);
    for (var i = 4; i < 16; i += 4) {
        hV[i] = hV[i >> 2].twice(1);
        hV[i + 1] = hV[i].add(hV[1]);
        hV[i + 2] = hV[i].add(hV[2]);
        hV[i + 3] = hV[i].add(hV[3]);
    }
    var t = Math.max(kr.bitLength(), ks.bitLength());
    var R = this.E.infinity;
    for (var i = (((t + 1) >> 1) << 1) - 1; i >= 0; i -= 2) {
        var j = (kr.testBit(i  ) ? 8 : 0) |
                (ks.testBit(i  ) ? 4 : 0) |
                (kr.testBit(i-1) ? 2 : 0) |
                (ks.testBit(i-1) ? 1 : 0);
        R = R.twice(2).add(hV[j]);
    }
    return R;
}

function negate() {
    return new JSPoint(this.E, this.x, (this.y.signum() !== 0) ? this.E.bn.p.subtract(this.y) : this.y, this.z);
}

/*
 * Compute k*this
 * This method implements the GLV strategy when no precomputed table is available,
 * otherwise the quaternary window method with precomputation.
 * @param {BigInteger} k scalar by which this point is to be multiplied
 * @return  k*this
 */

function multiply(k) {
    var bn = this.E.bn;
    var P = this.normalize();
    if (k.signum() < 0) {
        k = k.negate();
        P = P.negate();
    }
    var r = bn.u.shiftLeft(1).add(this.E.bn._1); // 2*u + 1
    var t = bn.u.multiply(this.E.bn._3).add(this.E.bn._1).multiply(bn.u.shiftLeft(1)); // (3*u + 1)*2*u = 6*u^2 + 2*u
    //
    var halfn = bn.r.shiftRight(1);
    var kr = k.multiply(r);
    if (kr.mod(bn.r).compareTo(halfn) <= 0) {
        kr = kr.divide(bn.r);
    } else {
        kr = kr.divide(bn.r).add(this.E.bn._1);
    }
    var kt = k.multiply(t);
    if (kt.mod(bn.r).compareTo(halfn) <= 0) {
        kt = kt.divide(bn.r);
    } else {
        kt = kt.divide(bn.r).add(this.E.bn._1);
    }
    //
    var sr = k.subtract(kr.multiply(r).add(kt.multiply(t.add(r))));
    var st = kr.multiply(t).subtract(kt.multiply(r));
    //
    var Y = new JSPoint(this.E, P.x.multiply(bn.zeta), P.y, P.z);
    return P.simultaneous(sr, st, Y);
}

JSPoint.prototype.isZero = isZero;
JSPoint.prototype.add = add;
JSPoint.prototype.normalize = normalize;
JSPoint.prototype.twice = twice;
JSPoint.prototype.simultaneous = simultaneous;
JSPoint.prototype.multiply = multiply;

